﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SharePoint;

namespace SharePointees.EnhancedLookupField.Tools
{
    public enum CAMLConditions
    {

        And,
        Or,
        None
    }

    public enum CAMLOperators
    {
        Eq,
        Neq,
        Gt,
        Lt,
        Geq,
        Leq,
        BeginsWith,
        Contains,
        IsNull,
        IsNotNull,
        None
    }

    public class CAMLParameter
    {
        public CAMLOperators Operator { get; set; }
        public CAMLConditions Condition { get; set; }
        public string FieldInternalName { get; set; }
        public string FieldValue { get; set; }
        public SPFieldType FieldValueType { get; set; }

        public CAMLParameter(CAMLOperators op, CAMLConditions condition, string fieldInternalName, string fieldValue)
        {
            Operator = op;
            Condition = condition;
            FieldInternalName = fieldInternalName;
            FieldValue = fieldValue;
            FieldValueType = SPFieldType.Text;
        }

        public CAMLParameter(CAMLOperators op, string fieldInternalName, string fieldValue)
        {
            Operator = op;
            Condition = CAMLConditions.None;
            FieldInternalName = fieldInternalName;
            FieldValue = fieldValue;
            FieldValueType = SPFieldType.Text;
        }

        public CAMLParameter(CAMLOperators op, CAMLConditions condition, string fieldInternalName, string fieldValue, SPFieldType fieldValueType)
        {
            Operator = op;
            Condition = condition;
            FieldInternalName = fieldInternalName;
            FieldValue = fieldValue;
            FieldValueType = fieldValueType;
        }
    }

    public static class CAMLUtilities
    {
        public const string CONDITION_TAG_FORMAT = "<{0}>{1}{2}</{0}>";
        public const string TAG_FORMAT = "<{0}>{1}</{0}>";
        public const string WHERE_TAG_FORMAT = "<Where>{0}</Where>";
        public const string FIELDREF_FORMAT = "<FieldRef Name='{0}' /><Value>{1}</Value>";
        public const string FIELDREF_FORMAT_WITH_TYPE = "<FieldRef Name='{0}' /><Value Type='{2}'>{1}</Value>";

        /// <summary>
        /// Retoure une balise FieldRef
        /// </summary>
        /// <param name="internalName"></param>
        /// <param name="value"></param>
        /// <param name="vType"></param>
        /// <returns></returns>
        private static string GetFieldRef(string internalName, string value)
        {
            return string.Format(FIELDREF_FORMAT, internalName, value);
        }

        /// <summary>
        /// Retoure une balise FieldRef
        /// </summary>
        /// <param name="internalName"></param>
        /// <param name="value"></param>
        /// <param name="vType"></param>
        /// <returns></returns>
        private static string GetFieldRef(string internalName, string value, string vType)
        {
            if (string.IsNullOrEmpty(vType))
                return GetFieldRef(internalName, value);
            return string.Format(FIELDREF_FORMAT_WITH_TYPE, internalName, value, vType);
        }

        /// <summary>
        /// Applique un opérateur au FieldRef et retourne la chaine complète
        /// </summary>
        /// <param name="param"></param>
        /// <returns></returns>
        private static string GetFieldWithSelectedOperator(CAMLOperators op, string fieldInternalName, string value)
        {
            return GetFieldWithSelectedOperator(op, GetFieldRef(fieldInternalName, value));
        }

        /// <summary>
        /// Applique un opérateur au FieldRef et retourne la chaine complète
        /// </summary>
        /// <param name="param"></param>
        /// <returns></returns>
        private static string GetFieldWithSelectedOperator(CAMLOperators op, string fieldInternalName, string value, string vType)
        {
            return GetFieldWithSelectedOperator(op, GetFieldRef(fieldInternalName, value, vType));
        }

        /// <summary>
        /// Applique un opérateur au FieldRef et retourne la chaine complète
        /// </summary>
        /// <param name="param"></param>
        /// <returns></returns>
        private static string GetFieldWithSelectedOperator(CAMLOperators op, string fieldref)
        {
            if (op == CAMLOperators.None)
                return fieldref;
            else
                return string.Format(TAG_FORMAT, op.ToString(), fieldref);
        }

        /// <summary>
        /// Applique un opérateur au FieldRef et retourne la chaine complète
        /// </summary>
        /// <param name="param"></param>
        /// <returns></returns>
        private static string GetFieldWithSelectedOperator(CAMLParameter param)
        {
            return GetFieldWithSelectedOperator(param.Operator, param.FieldInternalName, param.FieldValue, param.FieldValueType.ToString());
        }

        /// <summary>
        /// Applique une condition ET/OU à 2 fieldRef
        /// </summary>
        /// <param name="condition"></param>
        /// <param name="field1"></param>
        /// <param name="field2"></param>
        /// <returns></returns>
        private static string ApplyConditionToFields(CAMLConditions condition, string field1, string field2)
        {
            if (condition == CAMLConditions.None)
                return field1;
            else
                return string.Format(CONDITION_TAG_FORMAT, condition, field1, field2);
        }

        /// <summary>
        /// Construit la requête CAML correspondant à la liste de paramètres passée en argument.
        /// Ordonnez les éléments avant la génération de la requête
        /// </summary>
        /// <param name="parameters"></param>
        /// <returns></returns>
        /// <example>
        /// <![CDATA[
        /// List<CAMLParameter> parameters = new List<CAMLParameter>();
        ///    parameters.Add(new CAMLParameter(CAMLOperators.Geq, CAMLConditions.And, "Created", "2010-11-01", "DateTime"));
        ///    parameters.Add(new CAMLParameter(CAMLOperators.Leq, CAMLConditions.Or, "Created", "2010-11-29", "DateTime"));
        ///    parameters.Add(new CAMLParameter(CAMLOperators.Eq, CAMLConditions.None, "Suivi", "16", "Text"));
        ///    string caml = CAMLUtilities.ConstructCAMLQuery(parameters);]]>
        /// </example>
        public static string ConstructCAMLQuery(List<CAMLParameter> parameters)
        {
            string query = string.Empty;
            int position = 0;
            if (position == parameters.Count - 1)
                query = GetFieldWithSelectedOperator(parameters[position]);
            else
                query = ApplyConditionToFields(parameters[position].Condition, GetFieldWithSelectedOperator(parameters[position]), ConstructCAMLQuery((position + 1), parameters));
            return string.Format(WHERE_TAG_FORMAT, query);
        }

        /// <summary>
        /// Méthode récursive permettant de parcourir la liste de paramètre et de construire la requête
        /// </summary>
        /// <param name="position"></param>
        /// <param name="parameters"></param>
        /// <returns></returns>
        private static string ConstructCAMLQuery(int position, List<CAMLParameter> parameters)
        {
            if (position == parameters.Count - 1)
                return GetFieldWithSelectedOperator(parameters[position]);
            else
                return ApplyConditionToFields(parameters[position].Condition, GetFieldWithSelectedOperator(parameters[position]), ConstructCAMLQuery((position + 1), parameters));
        }
    }
}
